Skip to content

feat(relay): add delivery lifecycle events to RelayService (#55)#60

Merged
Taiizor merged 1 commit into
developfrom
feat/relay-delivery-events
Jun 3, 2026
Merged

feat(relay): add delivery lifecycle events to RelayService (#55)#60
Taiizor merged 1 commit into
developfrom
feat/relay-delivery-events

Conversation

@Taiizor
Copy link
Copy Markdown
Owner

@Taiizor Taiizor commented Jun 3, 2026

Summary

Implements delivery-lifecycle events on RelayService (closes #55) so callers can observe delivery outcomes — for example, to persist failures/successes to an external database.

Events (on RelayService)

Event Raised when
MessageDelivered Message delivered to all recipients
MessageBounced Permanent failure or retry limit reached — fires independently of NDR/bounce generation
MessageDeferred Temporary failure; message rescheduled for retry
MessageExpired Message exceeded its lifetime before delivery

All carry RelayDeliveryEventArgs (QueueId, From, Status, SmartHost, RetryCount, Result, Error, NextRetryTime, Timestamp). Raisers are wrapped in try/catch and log handler exceptions, matching the existing SmtpServer event convention.

Changes

  • New: RelayDeliveryEventArgs
  • RelayService: 4 events + On… raisers wired into DeliverMessageAsync
  • RelayService ctor: optional, backward-compatible Func<SmartHostConfiguration, ISmtpClient> client factory for testability
  • New: Zetian.Relay.Tests (10 tests) + registered in Zetian.slnx
  • README "Delivery Events" section + BasicRelayExample subscriptions

Testing

10/10 tests pass. BasicRelayExample verified MessageDeferred firing end-to-end against an unreachable smart host.

Known limitation

MessageExpired fires from the delivery path (DeliverMessageAsync), covering messages that expire and are re-dequeued. Messages swept purely by the background CleanupExpiredMessagesAsync (which uses IRelayQueue.ClearExpiredAsync(), returning only a count) do not raise the event. Closing that gap requires ClearExpiredAsync to return the removed messages — left as a small follow-up.

🤖 Generated with Claude Code

Add MessageDelivered, MessageBounced, MessageDeferred and MessageExpired
events to RelayService so callers can observe delivery outcomes, for example
to persist failures to an external database. Closes #55.

- Add RelayDeliveryEventArgs carrying the relay message, delivery result,
  error, retry count and next-retry time.
- Raise the events from DeliverMessageAsync at the delivered, permanent-
  failure, deferred and expired points. MessageBounced fires independently
  of NDR generation so failures are observable even when bounce messages
  are disabled.
- Add an optional ISmtpClient factory to the RelayService constructor
  (backward compatible) to make delivery deterministically testable.
- Add the Zetian.Relay.Tests project with 10 tests covering every event
  path and register it in the solution.
- Document the events in the relay README and demonstrate them in
  BasicRelayExample.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 3, 2026 06:46
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

🎉 Welcome, Contributor!

Thank you for submitting your first pull request to this repository! We're thrilled to see your contribution.

⏳ What Happens Next?

  • A maintainer will review your changes as soon as possible
  • You may receive feedback or requests for modifications
  • Once approved, your PR will be merged into the codebase

✅ PR Checklist

Before your PR can be merged, please ensure:

  • Your code follows the project's coding style and conventions
  • You've tested your changes locally
  • You've updated relevant documentation (if applicable)
  • All CI checks are passing
  • Your commits have clear and meaningful messages

💡 Tips for Success

  • Keep it focused: Smaller, focused PRs are easier to review
  • Describe your changes: Help reviewers understand what you did and why
  • Be responsive: Address feedback promptly to keep the review moving
  • Ask questions: Don't hesitate to ask if something is unclear

🔄 Need to Make Changes?

If you need to update your PR based on feedback:

  1. Make your changes locally
  2. Commit and push to the same branch
  3. The PR will automatically update

Your contribution makes this project better. Thank you for being part of our community! 💜

@Taiizor Taiizor mentioned this pull request Jun 3, 2026
3 tasks
@Taiizor Taiizor merged commit 92f2fd5 into develop Jun 3, 2026
3 of 5 checks passed
@Taiizor Taiizor deleted the feat/relay-delivery-events branch June 3, 2026 06:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds delivery-lifecycle observability to Zetian.Relay by introducing RelayService events (delivered/bounced/deferred/expired) and a shared RelayDeliveryEventArgs payload, with accompanying tests and documentation/examples to help callers persist delivery outcomes externally (per #55).

Changes:

  • Added RelayService delivery lifecycle events and event raisers, wired into DeliverMessageAsync.
  • Introduced RelayDeliveryEventArgs and a testable SMTP client factory hook on RelayService.
  • Added a new Zetian.Relay.Tests test project plus README/example updates demonstrating event subscriptions.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Zetian.slnx Registers the new relay test project in the solution.
tests/Zetian.Relay.Tests/Zetian.Relay.Tests.csproj Adds new test project for relay delivery event behavior.
tests/Zetian.Relay.Tests/RelayServiceEventTests.cs Adds tests validating event firing across success, temp/permanent failures, expiry, and handler exception swallowing.
src/Zetian.Relay/Zetian.Relay.csproj Exposes internals to the new test assembly.
src/Zetian.Relay/Services/RelayService.cs Implements the new events, raisers, and injectable client factory; wires event emission into delivery paths.
src/Zetian.Relay/README.MD Documents delivery events and provides subscription examples.
src/Zetian.Relay/Models/EventArgs/RelayDeliveryEventArgs.cs Adds the shared event args payload for delivery lifecycle events.
examples/Zetian.Relay.Examples/BasicRelayExample.cs Demonstrates event subscriptions in the basic relay example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +74 to +77
else
{
send.ReturnsAsync(() => sendResult!());
}
Comment on lines +336 to +340
relayService.MessageBounced += async (sender, e) =>
{
// Persist the failure to another database
await failureStore.SaveAsync(e.QueueId, e.From?.Address, e.Error, e.RetryCount);
};
Comment on lines +263 to +267
// Build a message whose lifetime has already elapsed so DeliverMessageAsync
// takes the expiry branch before any send attempt.
RelayMessage message = new(CreateMessage().Object, SmartHost, RelayPriority.Normal, TimeSpan.FromMilliseconds(1));
await Task.Delay(30);

Comment on lines +257 to 258
OnMessageExpired(new RelayDeliveryEventArgs(message) { Error = "Message expired" });
return;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Question]: Events on RelayService

2 participants